package nevo.pkgs.neuro.bio;

import java.io.*;
import java.util.*;

import nevo.core.*;

/**
 * A model that runs a script which understands at
 * least the following input arguments:
 * <ol>
 * 	<li>A unique ID which is used to specify part of the
 *  output file name</li>
 *  <li>The full path of the name-value pair file that
 *  specifies the input variables.</li>
 *  <li>The directory to write the output trace</li>
 *  <li>The sampling rate of the output trace (Hz)</li>
 *  <li>The current to inject (Amps)</li>
 *  <li>The duration of the current injection (ms)</li>
 *  <li>The start time of the current injection (ms)</li>
 * </ol>
 * The script is expected to write a voltage trace recording
 * from the soma to a file in the output directory named
 * "vtrace_[ID].data", where [ID] is the first argument passed
 * to the script.
 * 
 * @author mschachter
 *
 */
public class CurrentInjectionScriptModel implements Model
{
	protected static String FPFILE_PREFIX = "freeparams_";
	protected static String FPFILE_SUFFIX = ".props";
	protected static String OFILE_PREFIX = "vtrace_";
	protected static String OFILE_SUFFIX = ".data";
		
	/**
	 * How long the application took to run.
	 */
	protected int runtime;
	
	/**
	 * The command to execute.
	 */
	protected String command;
	
	/**
	 * The directory where the command is located.
	 */
	protected String workdir;
	
	/**
	 * The directory to write output (the voltage traces) to, passed
	 * as the second argument to the script.
	 */
	protected String outputDir;
	
	/**
	 * The currents (in Amps) that were injected to produce the real data. These
	 * are used as parts of the output file names. For example, this
	 * if a given current amplitude is specified as 1e-9 Amps, this class
	 * will look for the file "vtrace_1e-9_&lt;ID&gt;.data", where &lt;ID&gt;
	 * is the unique ID of the running process.
	 */
	protected double currentAmplitude;
	
	/**
	 * The duration of the current injection, in milliseconds.
	 */
	protected long currentDuration;
	
	/**
	 * The time to wait before beginning current injection, in milliseconds.
	 */
	protected long startTime;
	
	/**
	 * The sampling rate, in Hertz.
	 */
	protected long samplingRate;
		
	/**
	 * A pool of random numbers to pull from.
	 */
	protected RandomPool rpool;
	
	/**
	 * The arguments that are passed to the command.
	 */
	protected List<String> args;
	
	/**
	 * The interpreter of the script. If executing a .bat
	 * file on Windows this will be "cmd.exe", if executing
	 * a shell script on Linux this will be "/bin/sh", "/bin/bash",
	 * "/usr/bin/perl", etc.
	 */
	protected String interpreter;
	public void setInterpreter(String i) { interpreter = i; }
	public String getInterpreter() { return interpreter; }	
	
	
	public CurrentInjectionScriptModel()
	{
		rpool = new RandomPool(1000);
		args = new ArrayList<String>();		
	}
	
	
	public void setCurrentAmplitude(double ca) { currentAmplitude = ca; }
	public double getCurrentAmplitude() { return currentAmplitude; }	
	public void setCurrentDuration(long cd) { currentDuration = cd; }
	public long getCurrentDuration() { return currentDuration; }	
	public void setStartTime(long s) { startTime = s; }
	public long getStartTime() { return startTime; }
	public void setSamplingRate(long s) { samplingRate = s; }
	public long getSamplingRate() { return samplingRate; }
	public void setOutputDir(String s) { outputDir = s; }
	public String getOutputDir() { return outputDir; }	
	public void setArgs(List<String> a) { args = a; }
	public List<String> getArgs() { return args; }	
	public void setCommand(String c) { command = c;}
	public String getCommand() { return command;}	
	public void setWorkdir(String w) { workdir = w;}
	public String getWorkdir() { return workdir;}	
	public int getRuntime() { return runtime; }
	
	public boolean isParallel() { return true; }	
	
	public Map<String, Object> run(Map<String,Object> m) throws Exception
	{
		String id = "ID-" + ((Double) rpool.getRandom()).toString();
		String fpPath = writeFreeParamsFile(m, id);
		
		List<String> arglist = makeArgList(id, fpPath);		
		
		File odir = new File(workdir);
		if (!odir.exists())
			throw new Exception("Working directory '" + workdir + " doesn't exist!");
				
		ProcessBuilder pb = new ProcessBuilder();
		pb.command(arglist);
		pb.directory(odir);
		pb.redirectErrorStream(true);
		Process p = pb.start();
		BufferedInputStream bis = new BufferedInputStream(p.getInputStream());
		BufferedReader br = new BufferedReader(new InputStreamReader(bis));
		String line;
		while ((line = br.readLine()) != null) {
			System.err.println(line);
		}
		
		Map<String, Object> out = retrieveOutput(id);
		return out;
	}	
	
	protected Map<String, Object> retrieveOutput(String id) throws Exception
	{
		Map<String,Object> out = new HashMap<String,Object>();		
		String fpath = outputDir + File.separator + OFILE_PREFIX + id + OFILE_SUFFIX;;
		
		Double vtrace[] = Utilities.fileToArray(fpath);
		out.put("vtrace", vtrace);
		out.put("samplingRate", samplingRate);
			
		return out;
	}
	
	/*
	 * Overridden method from ScriptModel, inserts ID,
	 * output directory, and current to inject as first three
	 * arguments to command.
	 */
	protected List<String> makeArgList(String id, String fpPath) throws Exception
	{
		List<String> alist = new ArrayList<String>();		
		alist.add(0, interpreter);
		alist.add(1, command);
		alist.add(2, id);
		alist.add(3, fpPath);
		alist.add(4, outputDir);
		alist.add(5, ((Long) samplingRate).toString());
		alist.add(6, ((Double) currentAmplitude).toString());
		alist.add(7, ((Long) currentDuration).toString());
		alist.add(8, ((Long) startTime).toString());
		
		return alist;
	}
	
	protected String writeFreeParamsFile(Map<String,Object> min, String id) throws Exception
	{
		String path = outputDir + File.separator + FPFILE_PREFIX +
					  id + FPFILE_SUFFIX;
		
		File fp = new File(path);
		FileWriter fw = new FileWriter(fp);
		BufferedWriter writer = new BufferedWriter(fw);
		Object val;
		for (Map.Entry<String,Object> entry : min.entrySet()) {
			val = entry.getValue();
			if (val instanceof Double) {
				writer.write(entry.getKey() + "=" + ((Double) val).toString() + "\n");
			} else if (val instanceof Integer) {
				writer.write(entry.getKey() + "=" + ((Integer) val).toString() + "\n");
			}
		}
		writer.flush();
		writer.close();
		fw.close();		
		
		return path;
	}
}
